package cn.redragon.soa.security.keycloak;

import cn.redragon.soa.common.web.util.jwt.OAuth2Client;
import java.util.Collection;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnClass(value = {OAuth2Client.class, NimbusJwtDecoder.class, KeycloakSpringBootProperties.class})
public class KeycloakBasicAuthenticationProvider implements AuthenticationProvider {

    private final OAuth2Client oAuth2Client;

    private final JwtDecoder jwtDecoder;

    private final KeycloakSpringBootProperties keycloakProperties;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken upaToken = (UsernamePasswordAuthenticationToken) authentication;
        String token = oAuth2Client.getToken((String) upaToken.getPrincipal(), (String) upaToken.getCredentials());
        if (StringUtils.isBlank(token)) {
            throw new InvalidBearerTokenException("Invalid bearer token, token: " + token);
        }
        return this.createSuccessAuthentication(authentication, token);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    protected Authentication createSuccessAuthentication(Authentication authentication, String token) {
        Jwt jwt = jwtDecoder.decode(token);
        Collection<? extends GrantedAuthority> authorities = JwtHelper.extractResourceRoles(jwt, getResource(keycloakProperties));
        return new JwtAuthenticationToken(jwt, authorities, (String) authentication.getPrincipal());
    }
    
    public static String getResource(KeycloakSpringBootProperties keycloakProperties) {
        String resource =  Optional.of(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()))
            .map(ServletRequestAttributes::getRequest)
            .map(r -> r.getHeader("client_id"))
            .filter(StringUtils::isNotBlank)
            .orElse(keycloakProperties.getResource());
        return resource;
    }
}
